package br.com.hbt.expressaoregular.formula;

import java.util.HashMap;
import java.util.Stack;

public class CalculaFormula {

    private String expressao;

    private HashMap<String, String> operadores;

    private Contexto contexto;

    public static void main(String[] args) {

        //Pode-se instanciar vazio ou já com a expressão configurada
        CalculaFormula calc = new CalculaFormula("(b+c)/a");

        //Inicia e configura o contexto e os parametros da expressão
        Contexto ctx = new Contexto();
        ctx.atribui("a", 2);
        ctx.atribui("b", 4);
        ctx.atribui("c", 3);
        ctx.atribui("d", 1);

        //pode-se alterar a expressão
        calc.setExpressao("(((-(a*b))+c-d)/(z-(a+c)))");
        //(($12-$23)*($43-$23))*2/6

        //Configura o contexto na formula
        calc.setContexto(ctx);

        //Resultados

        System.out.println(calc.getContexto().getMapParam());
        System.out.println("Expressão Informada: " + calc.expressao);
        try {
            System.out.println("Resultado: " + calc.resultado());
        } catch (Exception e) {
            System.out.println("Erro: " + e);
        }

    }

    public CalculaFormula() {}

    public CalculaFormula(String formula) {
        setExpressao(formula);

        operadores = new HashMap<String, String>();
        operadores.put("+", "1");
        operadores.put("-", "1");
        operadores.put("/", "2");
        operadores.put("*", "2");
        operadores.put("(", "0");
    }

    private ExpressaoNaoFinal getExpressaoNaoFinal(String operacao,
            ExpressaoInterface esquerda, ExpressaoInterface direita) {
        if (operacao.trim().equals("+")) {
            return new Adicao(esquerda, direita);
        }
        if (operacao.trim().equals("-")) {
            return new Subtracao(esquerda, direita);
        }
        if (operacao.trim().equals("*")) {
            return new Multiplicacao(esquerda, direita);
        }
        if (operacao.trim().equals("/")) {
            return new Divisao(esquerda, direita);
        }

        return null;
    }

    private boolean isOperador(String str) {
        return ((str.equals("+")) || (str.equals("-")) || (str.equals("*")) || (str.equals("/")));
    }

    private ExpressaoInterface constroiArvore(String expr) throws RuntimeException {
        Stack<ExpressaoInterface> s = new Stack<ExpressaoInterface>();

        for (int indice = 0; indice < expr.length(); indice++) {
            String currChar = expr.substring(indice, indice + 1);
            System.out.println(currChar);

            if (isOperador(currChar)) {

                try {
                    ExpressaoInterface r = s.pop();
                    ExpressaoInterface l = s.pop();
                    ExpressaoInterface n = getExpressaoNaoFinal(currChar, l, r);
                    s.push(n);
                } catch (Exception e) {
                    throw new RuntimeException("Expressão Mal formada.");
                }
            } else {
                ExpressaoInterface e = new ExpressaoFinal(currChar);
                s.push(e);
            }
        }//for
        return s.pop();
    }

    private String notacaoPolonesaReversa(String expressaoInicial) throws RuntimeException {
        Stack<String> s = new Stack<String>();
        String expressaoPolonesa = "";
        String expressaoTemp = "";

        expressaoInicial = expressaoInicial.trim();
        for (int indice = 0; indice < expressaoInicial.length(); indice++) {

            String charAtual = expressaoInicial.substring(indice, indice + 1);

            if (!isOperador(charAtual) && (!charAtual.equals("("))
                    && (!charAtual.equals(")"))) {
                expressaoPolonesa = expressaoPolonesa + charAtual;
                //                System.out.println("expressaoTemp: " + charAtual);
            }
            if (charAtual.equals("(")) {
                s.push(charAtual);
            }
            //Para ')' pega todo conteudo até aparecer '('
            if (charAtual.equals(")")) {
                expressaoTemp = s.pop();
                while (!expressaoTemp.equals("(")) {
                    expressaoPolonesa = expressaoPolonesa + expressaoTemp;
                    expressaoTemp = s.pop();
                }
                expressaoTemp = "";
            }
            //Se o caractere atual é um operador
            if (isOperador(charAtual)) {
                if (!s.isEmpty()) {
                    expressaoTemp = s.pop();
                    String strVal1 = operadores.get(expressaoTemp);
                    double val1 = new Double(strVal1).doubleValue();
                    String strVal2 = operadores.get(charAtual);
                    double val2 = new Double(strVal2).doubleValue();

                    while ((val1 >= val2)) {
                        expressaoPolonesa = expressaoPolonesa + expressaoTemp;
                        val1 = -100;
                        if (!s.isEmpty()) {
                            expressaoTemp = s.pop();
                            strVal1 = operadores.get(expressaoTemp);
                            val1 = new Double(strVal1).doubleValue();

                        }
                    }
                    if ((val1 < val2) && (val1 != -100)) {
                        s.push(expressaoTemp);
                    }
                }
                s.push(charAtual);
            }//if

        }// for
        while (!s.isEmpty()) {
            expressaoTemp = s.pop();
            expressaoPolonesa = expressaoPolonesa + expressaoTemp;
        }
        return expressaoPolonesa;
    }

    public double resultado() throws RuntimeException {
        //Transformação para notação polonesa reversa
        String expressaoPosFixada = notacaoPolonesaReversa(expressao);

        System.out.println("Notação Polonesa: " + expressaoPosFixada);
        //Constroi a arvore binaria
        ExpressaoInterface nodoRaiz = constroiArvore(expressaoPosFixada);

        //Gera o resultado final
        return nodoRaiz.avaliaExpressao(contexto);
    }

    public Contexto getContexto() {
        return contexto;
    }

    public void setContexto(Contexto contexto) {
        this.contexto = contexto;
    }

    public String getExpressao() {
        return expressao;
    }

    public void setExpressao(String expressao) {

        this.expressao = expressao;
    }
}
